home *** CD-ROM | disk | FTP | other *** search
/ Monster Media 1996 #14 / Monster Media No. 14 (April 1996) (Monster Media, Inc.).ISO / prog_pas / tsfaqp29.zip / FAQPAS3.TXT < prev    next >
Internet Message Format  |  1996-01-01  |  66KB

  1. From ts@uwasa.fi Mon Jan 1 00:00:00 1996
  2. Subject: FAQPAS3.TXT contents
  3.  
  4.                                Copyright (c) 1993-1996 by Timo Salmi
  5.                                                  All rights reserved
  6.  
  7. FAQPAS3.TXT The third set of frequently (and not so frequently)
  8. asked Turbo Pascal questions with Timo's answers. The items are in
  9. no particular order.
  10.  
  11. You are free to quote brief passages from this file provided you
  12. clearly indicate the source with a proper acknowledgment.
  13.  
  14. Comments and corrections are solicited. But if you wish to have
  15. individual Turbo Pascal consultation, please post your questions to
  16. a suitable Usenet newsgroup like news:comp.lang.pascal.borland. It
  17. is much more efficient than asking me by email. I'd like to help,
  18. but I am very pressed for time. I prefer to pick the questions I
  19. answer from the Usenet news. Thus I can answer publicly at one go if
  20. I happen to have an answer. Besides, newsgroups have a number of
  21. readers who might know a better or an alternative answer. Don't be
  22. discouraged, though, if you get a reply like this from me. I am
  23. always glad to hear from fellow Turbo Pascal users.
  24.  
  25. ....................................................................
  26. Prof. Timo Salmi   Co-moderator of news:comp.archives.msdos.announce
  27. Moderating at ftp:// & http://garbo.uwasa.fi archives  193.166.120.5
  28. Department of Accounting and Business Finance  ; University of Vaasa
  29. ts@uwasa.fi http://uwasa.fi/~ts BBS 961-3170972; FIN-65101,  Finland
  30.  
  31. --------------------------------------------------------------------
  32. 61) What are Binary Coded Decimals? How to convert them?
  33. 62) How can I copy a file in a Turbo Pascal program?
  34. 63) How can I use C code in my Turbo Pascal program?
  35. 64) How do I get started with the Turbo Profiler?
  36. 65) How can I detect if the shift/ctrl/alt etc key is pressed?
  37. 66) How do I get a base 10 logarithm in TP?
  38. 67) If Delay procedure does not work properly, how do I fix it?
  39. 68) How much memory will my TP program require?
  40. 69) How to detect if a drive is a CD-ROM drive?
  41. 70) How do I convert an array of characters into a string?
  42. 71) How do I get started with graphics programming?
  43. 72) Where to I find the different sorting source codes?
  44. 73) A beginner's how to write and compile units.
  45. 74) What are and how do I use pointers?
  46. 75) How can I read another program's errorlevel value in TP?
  47. 76) What are the current Pascal newsgroups on the Usenet news?
  48. 77) How do I detect the CapsLock status, how do I turn it on/off?
  49. 78) How do I detect if the F11 or F12 key has been pressed?
  50. 79) How do I extract (parse) substrings from an input string?
  51. 80) How do I find out the size of any kind of a file?
  52. 81) How do I format graphics output like in textmode writeln?
  53. 82) How do I detect if more than one standard key is pressed down?
  54. 83) How can I read a disk's Volume Serial Number?
  55. 84) How can I disable and then enable the keyboard in my TP program?
  56. 85) How do I get the character device name of the (first) CD-ROM?
  57. 86) How do I eject a CD-ROM using a Turbo Pascal program?
  58. 87) How do I find out if the ANSI.SYS driver has been loaded?
  59. 88) Where do I find Turbo Pascal tutorials and/or good textbooks?
  60. 89) How dow I make an executable of my Turbo Pascal source program?
  61. 90) How can I quickly read the last byte of a file?
  62. --------------------------------------------------------------------
  63.  
  64. From ts@uwasa.fi Mon Jan 1 00:01:01 1996
  65. Subject: Binary Coded Decimals
  66.  
  67. 61. *****
  68.  Q: What are Binary Coded Decimals? How to convert them?
  69.  
  70.  A: Let us look at full integers only and skip the even more
  71. difficult question of BCD reals and BCD operations.
  72.      Decimal Hexa  BCD
  73.         1      $1    1
  74.         :      $9    9
  75.        10      $A   ..
  76.         :       :    :
  77.        12      $C   ..
  78.         :       :    :
  79.        16     $10   10
  80.        17     $11   11
  81.        18     $12   12
  82.         :       :    :
  83. Consider the last value, that is BCD presentation of 12. The
  84. corresponding hexadecimal is $12 (not $C as in normal decimal to
  85. hexadecimal conversion). The crucial question is how to convert
  86. 12BCD to $12 (or its normal decimal equivalent 18). Here is my
  87. sample code:
  88.   type BCDType = array [0..7] of char;
  89.   {}
  90.   procedure StrToBCD (s : string; var b : BCDType);
  91.   var i, p : byte;
  92.   begin
  93.     FillChar(b, SizeOf(b), '0');
  94.     p := Length (s);
  95.     if p > 8 then exit;
  96.     for i := p downto 1 do b[p-i] := s[i];
  97.   end;  (* strtobcd *)
  98.   {}
  99.   function BCDtoDec (b : BCDType; var ok : boolean) : longint;
  100.   const Digit : array [0..9] of char = '0123456789';
  101.   var i, k : byte;
  102.       y, d : longint;
  103.   begin
  104.     y := 0;
  105.     d := 1;
  106.     ok := false;
  107.     for i := 0 to 7 do begin
  108.       k := Pos (b[i], Digit);
  109.       if k = 0 then exit;
  110.       y := y + (k-1) * d;
  111.       if i < 7 then d := 16 * d;
  112.     end; { for }
  113.     ok := true;
  114.     BCDtoDec := y;
  115.   end;  (* bcdtodec *)
  116.   {}
  117.   {}
  118.   procedure TEST;
  119.   var i  : byte;
  120.       b  : BCDType;
  121.       x  : longint;
  122.       ok : boolean;
  123.       s  : string;
  124.   begin
  125.     s := '12';
  126.     StrToBCD (s, b);
  127.     write ('The BCD value : ');
  128.     for i := 7 downto 0 do write (b[i], ' ');
  129.     writeln;
  130.     x := BCDtoDec (b, ok);
  131.     if ok then writeln ('is ', x, ' as an ordinary decimal')
  132.       else writeln ('Error in BCD');
  133.   end;  (* test *)
  134.   {}
  135.   begin TEST; end.
  136.  
  137. Next we can ask, what if the BCD value is given as an integer.
  138. Simple, first convert the integer into a string. For example in
  139. the procedure TEST put
  140.   Str (12, s);
  141.  
  142. Finally, what about converting an ordinary decimal to the
  143. corresponding BCD but given also as a decimal variable.  For example
  144. 18 --> 12?
  145.   function LHEXFN (decimal : longint) : string;
  146.   const hexDigit : array [0..15] of char = '0123456789ABCDEF';
  147.   var i : byte;
  148.       s : string;
  149.   begin
  150.     FillChar (s, SizeOf(s), ' ');
  151.     s[0] := chr(8);
  152.     for i := 0 to 7 do
  153.       s[8-i] := HexDigit[(decimal shr (4*i)) and $0F];
  154.     lhexfn := s;
  155.   end;  (* lhexfn *)
  156.   {}
  157.   function DecToBCD (x : longint; var ok : boolean) : longint;
  158.   const Digit : array [0..9] of char = '0123456789';
  159.   var hexStr : string;
  160.   var i, k : byte;
  161.       y, d : longint;
  162.   begin
  163.     hexStr := LHEXFN(x);
  164.     y := 0;
  165.     d := 1;
  166.     ok := false;
  167.     for i := 7 downto 0 do begin
  168.       k := Pos (hexStr[i+1], Digit);
  169.       if k = 0 then exit;
  170.       y := y + (k-1) * d;
  171.       if i > 0 then d := 10 * d;
  172.     end; { for }
  173.     ok := true;
  174.     DecToBCD := y;
  175.   end;  (* dectobcd *)
  176.   {}
  177.   procedure TEST2;
  178.   var i    : byte;
  179.       x10  : longint;
  180.       xBCD : longint;
  181.       ok   : boolean;
  182.   begin
  183.     x10 := 18;
  184.     writeln ('The ordinary decimal value : ', x10);
  185.     xBCD := DecToBCD (x10, ok);
  186.     if ok then writeln ('is ', xBCD, ' as a binary coded decimal')
  187.       else writeln ('Error in BCD');
  188.   end;  (* test2 *)
  189.   {}
  190.   begin TEST; end.
  191. --------------------------------------------------------------------
  192.  
  193. From ts@uwasa.fi Mon Jan 1 00:01:02 1996
  194. Subject: Copying with TP
  195.  
  196. 62. *****
  197.  Q: How can I copy a file in a Turbo Pascal program?
  198.  
  199.  A: Here is the code. Take a close look. It has some instructive
  200. features besides the copying, like handling the filemode and using
  201. dynamic variables (using pointers). Note that since the buffer for
  202. the copying is places on the heap you must reserve enough heap. For
  203. example you might have {$M 16384,0,102400}.
  204.   procedure SAFECOPY (fromFile, toFile : string);
  205.   type bufferType = array [1..65535] of char;
  206.   type bufferTypePtr = ^bufferType;  { Use the heap }
  207.   var bufferPtr : bufferTypePtr;     { for the buffer }
  208.       f1, f2 : file;
  209.       bufferSize, readCount, writeCount : word;
  210.       fmSave : byte;              { To store the filemode }
  211.   begin
  212.     bufferSize := SizeOf(bufferType);
  213.     if MaxAvail < bufferSize then exit;  { Assure there is enough memory }
  214.     New (bufferPtr);              { Create the buffer, on the heap }
  215.     fmSave := FileMode;           { Store the filemode }
  216.     FileMode := 0;                { To read also read-only files }
  217.     Assign (f1, fromFile);
  218.     {$I-} Reset (f1, 1); {$I+}    { Note the record size 1, important! }
  219.     if IOResult <> 0 then exit;   { Does the file exist? }
  220.     Assign (f2, toFile);
  221.     {$I-} Reset (f2, 1); {$I+}    { Don't copy on an existing file }
  222.     if IOResult = 0 then begin close (f2); exit; end;
  223.     {$I-} Rewrite (f2, 1); {$I+}  { Open the target }
  224.     if IOResult <> 0 then exit;
  225.     repeat                        { Do the copying }
  226.       BlockRead (f1, bufferPtr^, bufferSize, readCount);
  227.       {$I-} BlockWrite (f2, bufferPtr^, readCount, writeCount); {$I+}
  228.       if IOResult <> 0 then begin close (f1); exit; end;
  229.     until (readCount = 0) or (writeCount <> readCount);
  230.     writeln ('Copied ', fromFile, ' to ', toFile,
  231.              ' ', FileSize(f2), ' bytes');
  232.     close (f1); close (f2);
  233.     FileMode := fmSave;           { Restore the original filemode }
  234.     Dispose (bufferPtr);          { Release the buffer from the heap }
  235.   end;  (* safecopy *)
  236.  
  237. Of course a trivial solution would be to invoke the MS-DOS copy
  238. command using the Exec routine. (See the item "How do I execute an
  239. MS-DOS command from within a TP program?")
  240. --------------------------------------------------------------------
  241.  
  242. From ts@uwasa.fi Mon Jan 1 00:01:03 1996
  243. Subject: C modules in TP
  244.  
  245. 63. *****
  246.  Q: How can I use C code in my Turbo Pascal program?
  247.  
  248.  A: I have very little information on this question, since I do not
  249. program in C myself.  However in reading Turbo Pascal textbooks I
  250. have come across a couple of references I can give.  They are Edward
  251. Mitchell (1993), Borland Pascal Developer's Guide, pp. 60-64, and
  252. Stoker & Ohlsen (1989), Turbo Pascal Advanced Techniques, Ch 4.
  253. --------------------------------------------------------------------
  254.  
  255. From ts@uwasa.fi Mon Jan 1 00:01:04 1996
  256. Subject: Using Turbo Profiler
  257.  
  258. 64. *****
  259.  Q: How do I get started with the Turbo Profiler?
  260.  
  261.  A: Borland's separate Turbo Profiler is a powerful tool for
  262. improving program code and enhancing program performance, but far
  263. from an easy to use. It is an advanced tool. In fact setting it up
  264. the first time is almost a kind of detective work.
  265.    Let's walk through the steps with Turbo Profiler version 1.01 to
  266. see where a running Turbo Pascal program takes its time.
  267. Assume a working directory r:\
  268. 1. Copy the target .PAS file to r:\
  269. 2. Compile it with TURBO.EXE using the following Compiler and
  270.    Debugger options. The standalone debugging option is crucial.
  271.      Code generation
  272.       [ ] Force far calls        [X] Word align data
  273.       [ ] Overlays allowed       [ ] 286 instructions
  274.      Runtime errors             Syntax options
  275.       [ ] Range checking         [X] Strict var-strings
  276.       [X] Stack checking         [ ] Complete boolean eval
  277.       [ ] I/O checking           [X] Extended syntax
  278.       [ ] Overflow checking      [ ] Typed @ operator
  279.                                  [ ] Open parameters
  280.      Debugging
  281.       [X] Debug information     Numeric processing
  282.       [X] Local symbols          [ ] 8087/80287
  283.                                  [ ] Emulation
  284.      Debugging         Display swapping
  285.       [X] Integrated    ( ) None
  286.       [X] Standalone    () Smart
  287.                         ( ) Always
  288. 3) Call TPROF.EXE
  289. 4) Load the .EXE file produced by compilation in item 2.
  290. 5) Choose from the TPROF menus
  291.      Statistics
  292.        Profiling options...
  293.          Profile mode
  294.           () Active    ( ) Passive
  295.           Run count
  296.            1
  297.           Maximum areas
  298.            200
  299. 6) Choose from the TPROF menus
  300.       Options
  301.        Save options...
  302.       [X] Options
  303.       [ ] Layout
  304.       [ ] Macros
  305.      Save To
  306.       r:\tfconfig.tf
  307. 7) Press Alt-F10 for the Local Menu. Choose
  308.      Add areas
  309.        All routines
  310. and so on.
  311. 8) Choose Run from the TPROF menus (or F9)
  312. 9) Choose from the TPROF menus
  313.       Print
  314.        Options...
  315.      Width
  316.       80
  317.      Height
  318.       9999
  319.       ( ) Printer     ( ) Graphics
  320.       () File        () ASCII
  321.      Destination File
  322.       r:\report.lst
  323. 10) Print
  324.        Module...
  325.          All modules
  326.          Statistics
  327.            Overwrite
  328. Also see Edward Mitchell (1993), Borland Pascal Developer's Guide.
  329. It has a a very instructive chapter "Program Optimization" on the
  330. Turbo Profiler. The material in the Turbo Profiler manual is so
  331. complicated that additional guidance like Mitchell's is very much
  332. needed.
  333. --------------------------------------------------------------------
  334.  
  335. From ts@uwasa.fi Mon Jan 1 00:01:05 1996
  336. Subject: Detecting shift status
  337.  
  338. 65. *****
  339.  Q: How can I detect if the shift/ctrl/alt etc key is pressed? I
  340. know how to get the scan codes with the ReadKey function, but I
  341. can't find the procedure for detecting these keys.
  342.  
  343.  A: Detecting pressing the special keys or getting the toggle status
  344. cannot be done with ReadKey. You'll need to access the Keyboard
  345. Flags Byte at $0040:$0017. You can do this either by a direct "Mem"
  346. access, or using interrupt $16 function $02. For more details
  347. including the bitfields for the shift flags see in Ralf Brown's
  348. interrupt list ftp://garbo.uwasa.fi/pc/programming/inter48a.zip (or
  349. whatever is the current version). For example to see if the alt key
  350. is pressed you can use
  351.   uses Dos;
  352.   function ALTDOWN : boolean;
  353.   var regs : registers;
  354.   begin
  355.     FillChar (regs, SizeOf(regs), 0);
  356.     regs.ah := $02;
  357.     Intr ($16, regs);
  358.     altdown := (regs.al and $08) = $08;
  359.   end;
  360. For the enhanced keyboard flags see interrupt $16 function $12. It
  361. can distinguish also between the right and the left alt and ctlr
  362. keys.
  363.    A tip from Martijn Leisink martijnl@sci.kun.nl. Be careful [if
  364. you use the $0040:$0017 memory position to set a toggle]: On several
  365. computers you have to call int 16h after the new setting is shown by
  366. the LED's on the keyboard. Not doing so might give the user wrong
  367. information.
  368.    A tip from Dr John Stockton jrs@dclf.npl.co.uk. Going via a
  369. BytePointer set to Ptr(Seg0040, $0017) is almost as easy as "Mem",
  370. and also works in Protected mode.
  371. --------------------------------------------------------------------
  372.  
  373. From ts@uwasa.fi Mon Jan 1 00:01:06 1996
  374. Subject: Base 10 logarithm
  375.  
  376. 66. *****
  377.  Q: How do I get a base 10 logarithm in TP?
  378.  
  379.  A: Just define
  380.      function log (x : real) : real;
  381.      begin log := ln(x) / ln(10); end;
  382. This result is based on some elementary math. By definition
  383. y = log(x) in base 10 is equivalent to x = 10^y (where the ^
  384. indicates an exponent). Thus ln(x) = y ln(10) and hence
  385. y = ln(x) / ln(10).
  386. --------------------------------------------------------------------
  387.  
  388. From ts@uwasa.fi Mon Jan 1 00:01:07 1996
  389. Subject: Replacing Delay procedure
  390.  
  391. 67. *****
  392.  Q: If Delay procedure does not work properly, how do I fix it?
  393.  
  394.  A: The Delay procedure in the Crt unit delays a specified number of
  395. milliseconds. It is declared as "procedure Delay(MS: Word);". There
  396. are two problems. The procedure requires using the Crt unit and
  397. there is a bug in it in TP 6.0, at least. The alternative is to use
  398. the procedure GetTime(var Hour, Minute, Second, Sec100: Word) as
  399. shown by the skeleton below
  400.   GetTime (...)
  401.   initialTime := ...
  402.   repeat
  403.     GetTime (...)
  404.     interval := ... - initialTime;
  405.   until interval >= YourDelay;
  406. There are two things you will have to see to. You will have to
  407. convert the time to sec100, and you will have to take care of the
  408. possibility of the interval spanning the midnight. If you do not
  409. wish to program the alternative Delay procedure yourself, you can
  410. use "DOSDELAY Delay without needing the Crt unit" from TSUNTD.TPU
  411. from ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip.
  412.  
  413.  A2: Dr John Stockton jrs@dclf.npl.co.uk suggested procedure that is
  414. expanded below. It has the advantage of being concise and working in
  415. the protected mode. The disadvantage is that it requires a later TP
  416. version. The solution is quite instructive.
  417.   uses Dos;
  418.   {... John's procedure ...}
  419.   procedure WAIT (SecondsDelay : real) ;
  420.   Var Tptr : ^longint ; Finish : longint ;
  421.   begin
  422.     Tptr := Ptr(Seg0040, $006C) ;
  423.     Finish := Tptr^ + Round(18.2*SecondsDelay) ;
  424.     repeat until Tptr^ > Finish ;
  425.   end;
  426.   {... now let's test it ...}
  427.   var h1, m1, s1, sa100 : word;
  428.       h2, m2, s2, sb100 : word;
  429.   begin
  430.     GetTime (h1, m1, s1, sa100);
  431.     WAIT (3);
  432.     GetTime (h2, m2, s2, sb100);
  433.     writeln (h1, ':', m1, ':', s1, '.' ,sa100);
  434.     writeln (h2, ':', m2, ':', s2, '.' ,sb100);
  435.   end.
  436. --------------------------------------------------------------------
  437.  
  438. From ts@uwasa.fi Mon Jan 1 00:01:08 1996
  439. Subject: TP program memory requirement
  440.  
  441. 68. *****
  442.  Q: How much memory will my TP program require?
  443.  
  444.  A: Get MAPMEM.EXE from ftp://garbo.uwasa.fi/pc/memutil/tsrcom35.zip
  445. and put the following code within your Turbo Pascal program:
  446.   Program faq;
  447.   uses Dos;
  448.   :
  449.   SwapVectors;
  450.   Exec (GetEnv('comspec'), '/c mapmem');
  451.   Swapvectors;
  452. Then you'll see a MAPMEM output something like this
  453.   Psp  Cnt   Size Name       Command Line        Hooked Vectors
  454.   ---- --- ------ ---------- ------------------- --------------
  455.         2  26,896 DOS
  456.   0694  2   3,392 COMMAND                        2E
  457.         1      64 ---free---
  458.   0776  2   1,488 MARK       scrollit
  459.   07D6  2  70,816 FAQ                            FF
  460.   1923  3   2,752 command                        22 23 24
  461.   19D2  2 549,712 ---free---
  462.           655,344 ---total--
  463. The memory requirement of your program FAQ.PAS is 70,816. Do not
  464. confuse this figure with the physica size of your program. The
  465. memory requirement affected among other things by the Memory
  466. Allocation Sizes Directive. For example you might have
  467. {$M 16384,0,50000}
  468.  
  469. -Date: Sun, 12 Jun 1994 10:22:18
  470. -From: dmurdoch@mast.queensu.ca (Duncan Murdoch)
  471. -Newsgroups: comp.lang.pascal
  472. -Subject: Re: How much memory will my TP program require?
  473.  
  474.    I think this is a hard question, and probably needs a longer
  475. answer than you gave.  Yours isn't quite right, because TP will
  476. allocate memory that it doesn't need if you set the heapmax
  477. parameter too high.  Your program will run in less memory than
  478. MAPMEM reports. Here's a quick attempt at it:
  479.    TP DOS programs use memory in 4 or 5 blocks:  fixed code, static
  480. data, the stack, sometimes overlaid code, and the heap.  TP Windows
  481. programs add a local heap to this list, but don't use overlays.  The
  482. discussion below deals with real mode DOS programs.
  483.    The size of the code is determined by which procedures and
  484. functions you use in your program.  In DOS, if you don't use
  485. overlays, this is all fixed code, and the size is reported as "Code
  486. size" in the Compile| Information listing in the IDE.  The ways to
  487. reduce it are to use fewer procedures or make them smaller, or move
  488. them to overlays.
  489.    Static data consists of all the global variables and typed
  490. constants in every unit.  It is reported as "Data size" in the
  491. Compile|Information listing.  You can reduce it by declaring fewer
  492. or smaller variables.
  493.    If you use the $O directive to move code to overlays, then those
  494. units won't count as part of your fixed code needs.  You will need
  495. an overlay buffer at run-time; by default, it's the size of the
  496. largest unit you use, but normally you'll change the size with
  497. OvrSetBuf.  It's difficult to work out the best size of this block
  498. except by trial and error:  if your program spends too much time
  499. swapping, then make it larger; if you run out of memory, make it
  500. smaller.  You'll need to use the .MAP file (see the Options| Linker
  501. dialog to create one) to find the size of each unit.  Remember to
  502. subtract the size of overlaid units from the reported "Code size"
  503. when working out the size of fixed code.
  504.   The stack is used for local variables in procedures.  Its size is
  505. controlled by the first parameter to the $M directive; the default
  506. size is 16K.  It's hard to predict exactly how much stack space your
  507. program will use.  One way is to keep reducing the value until your
  508. program aborts with a stack overflow, then use a slightly larger
  509. value.  Another way is to fill the stack with a fixed value at the
  510. start of your program, and at the end, see how many values were
  511. changed.  Again, it's a good idea to allow for a margin of safety,
  512. because hardware interrupts will use this space, and their size is
  513. hard to predict.
  514.    The heap is where New and Getmem get their allocated memory.  The
  515. size is controlled by the 2nd and 3rd parameters to the $M
  516. directive.  The heapmin value will always be allocated; if extra
  517. memory is available, your program will ask for as much as possible,
  518. up to heapmax.  If not enough memory is available to load all your
  519. fixed code, data, stack and heapmin, DOS will refuse to load your
  520. program.  You have nearly complete control over the size of the heap
  521. that you need, determined by how much you use New and Getmem. The
  522. only exception is that some of the standard units use heap space;
  523. GRAPH and all the TurboVision units are examples.  To find how much
  524. your program actually uses, you can reduce Heapmax until it fails,
  525. fill the heap with a special value and look for changes, or monitor
  526. the value of HeapPtr as your program progresses.
  527. --------------------------------------------------------------------
  528.  
  529. From ts@uwasa.fi Mon Jan 1 00:01:09 1996
  530. Subject: Detecting a CD-ROM drive?
  531.  
  532. 69. *****
  533.  Q: How to detect if a drive is a CD-ROM drive?
  534.  
  535.  A: There are several methods to do this. Here is one option.
  536.   (* Is a drive a CD-ROM with MSCDEX driver installed *)
  537.   function CDROMFN (drive : char) : boolean;
  538.   var regs : registers;
  539.   begin
  540.     cdromfn := false;
  541.     if swap(DosVersion) < $0200 then exit;
  542.     drive := UpCase(drive);
  543.     if (drive < 'A') or (drive > 'Z') then exit;
  544.     FillChar (regs, SizeOf(regs), 0);
  545.     regs.cx := ord(drive) - ord('A');
  546.     regs.ax := $150B;
  547.     Intr ($2F, regs);
  548.     cdromfn := (regs.ax <> 0) and (regs.bx = $ADAD);
  549.   end;  (* cdromfn *)
  550. The other relevant $2F interrupt functions you can use are $1500,
  551. $1501, and in particular $150D.
  552. --------------------------------------------------------------------
  553.  
  554. From ts@uwasa.fi Mon Jan 1 00:01:10 1996
  555. Subject: Array of chars into string
  556.  
  557. 70. *****
  558.  Q: How do I convert an array of characters to a string? More
  559. specifically, I haven't been able to convert an array of characters
  560. into a string, so that I can write it to a file. The only way I have
  561. been able to do it, is writing 1 char at a time.
  562.  
  563.  A: Carefully study these two simple test examples.  Note the
  564. difference in the array's dimensions in the tests.
  565.  
  566.   type atype = array [0..20] of char;
  567.   type stype = string[20];
  568.   var s : stype;
  569.       a : atype absolute s;
  570.   begin
  571.     FillChar (a, SizeOf(a), '*');
  572.     s[0] := chr(20);
  573.     writeln (s);
  574.   end.
  575.  
  576.   type atype = array [1..20] of char;
  577.   var s : string;
  578.       a : atype;
  579.   begin
  580.     FillChar (a, Sizeof(a), '*');
  581.     Move (a, s[1], 20);
  582.     s[0] := chr(20);
  583.     writeln (s);
  584.   end.
  585.  
  586. Of course, you could also assign the array's characters one by one
  587. to the string using a simple for loop (left as an exercise), but the
  588. above methods are more efficient.
  589. --------------------------------------------------------------------
  590.  
  591. From ts@uwasa.fi Mon Jan 1 00:01:11 1996
  592. Subject: Graphics programming primer
  593.  
  594. 71. *****
  595.  Q: How do I get started with graphics programming?
  596.  
  597.  A:
  598.   (* This simple test shows the rudiments of getting started with Turbo
  599.      Pascal graphics programming *)
  600.   uses Crt, Graph;
  601.   var grDriver : integer;
  602.       grMode   : integer;
  603.       ErrCode  : integer;
  604.       i, j     : integer;
  605.       xm, ym   : integer;
  606.   const CharSize : integer = 3;
  607.   begin
  608.     { Request graphics driver autodetection }
  609.     grDriver := Detect;
  610.     { Initialize graphics system and put hardware into graphics mode }
  611.     { The relevant .bgi driver is needed in the current directory
  612.       for example egavga.bgi }
  613.     InitGraph (grDriver, grMode, ' ');
  614.     { Return an error code for the previous graphic operation }
  615.     ErrCode := GraphResult;
  616.     { Test for initialialization success }
  617.     if ErrCode <> grOk then begin
  618.       Writeln ('Graphics error:', GraphErrorMsg(ErrCode)); halt; end;
  619.     { Clear the output device and home the current pointer }
  620.     ClearDevice;
  621.     {}
  622.     { Use your own coordinates }
  623.     xm := Round (GetMaxX / 100.0);
  624.     ym := Round (GetMaxY / 100.0);
  625.     {}
  626.     { Set the current line width and style, optional }
  627.     SetLineStyle (SolidLn, 0, ThickWidth);
  628.     { Set the drawing color }
  629.     SetColor (Yellow);
  630.     { Draw a line }
  631.     Line (70*xm, 50*ym, 90*xm, 80*ym);
  632.     {}
  633.     { Drawing bars }
  634.     { Set the fill pattern and color }
  635.     SetFillStyle (SolidFill, Red);
  636.     Bar (0, 0, 25*xm, 25*ym);
  637.     {}
  638.     SetColor (Magenta);
  639.     SetFillStyle (SolidFill, Blue);
  640.     Bar3D (30*xm, 20*ym, 50*xm, 60*ym, 8*xm, TopOn);
  641.     {}
  642.     { Writing text in the graphics mode }
  643.     { Set the drawing color }
  644.     SetColor (LightCyan);
  645.     { Set the current background color }
  646.     SetBkColor (Black);
  647.     { Set style for text output in graphics mode }
  648.     SetTextStyle(DefaultFont, HorizDir, CharSize);
  649.     OutTextXY (0, 80*ym, 'Press any key');
  650.     {}
  651.     repeat until KeyPressed;
  652.     {}
  653.     { Restore the original screen mode before graphics was initialized }
  654.     RestoreCrtMode;
  655.     writeln ('That''s all folks');
  656.     { Shut down the graphics system }
  657.     CloseGraph;
  658.   end.
  659. For an example what you can do with graphics, see
  660.  111673 Oct 8 1993 ftp://garbo.uwasa.fi/pc/ts/tsdemo16.zip
  661.  tsdemo16.zip Assorted graphics demonstrations of functions etc
  662. (or whatever is the current version).
  663. --------------------------------------------------------------------
  664.  
  665. From ts@uwasa.fi Mon Jan 1 00:01:12 1996
  666. Subject: Sorting it out
  667.  
  668. 72. *****
  669.  Q: Where to I find the different sorting source codes?
  670.  
  671.  A: I'll answer very briefly by giving two references:
  672.  303771 May 2 1991 ftp://garbo.uwasa.fi/pc/turbopas/nrpas13.zip
  673.  nrpas13.zip Numerical Recipes Pascal shareware version
  674. and
  675.  Gary Martin (1992), Turbo Pascal, Theory and Practice of Good
  676. Programming, Chapter 15.
  677. --------------------------------------------------------------------
  678.  
  679. From ts@uwasa.fi Mon Jan 1 00:01:13 1996
  680. Subject: TP units
  681.  
  682. 73. *****
  683.  Q: A beginner's how to write and compile units.
  684.  
  685.  A1: Many of the text-books in the bibliography section of this FAQ
  686. discuss using units in Turbo Pascal. For example see Tom Swan
  687. (1989), Mastering Turbo Pascal 5.5, Chapters 9 and 10 for a more
  688. detailed discussion than the rudiments given in the current item.
  689. Likewise see your Turbo Pascal (7.0) User's Guide Chapter 6, "Turbo
  690. Pascal units".
  691.    You can and need to write your own units if you need recurring or
  692. common routines in your programs and/or your program becomes so big
  693. that it cannot be handled as a single entity.
  694.    A Turbo Pascal unit is a separate file which you compile. The
  695. following trivial example to calculate the sum of two reals
  696. illustrates the basic structure of a unit.
  697.   { The name of this file must be faq73.pas to correspond. }
  698.   unit faq73;
  699.   {}
  700.   { The interface section lists definitions and routines that are }
  701.   { available to the other programs or units. }
  702.   interface
  703.   function SUMFN (a, b : real) : real;
  704.   {}
  705.   { The implementation section contains the actual unit program }
  706.   implementation
  707.   function SUMFN (a, b : real) : real;
  708.   begin
  709.     sumfn := a + b;
  710.   end;
  711.   {}
  712.   end.
  713. When you compile the file FAQ73.PAS a unit FAQ73.TPU results. Next
  714. an example utilizing the faq73 unit in the main program.
  715.   uses faq73;
  716.   {}
  717.   procedure TEST;
  718.   var x, y, z : real;
  719.   begin
  720.      x := 12.34;
  721.      y := 56.78;
  722.      z := SUMFN (x, y);
  723.      writeln (z);
  724.   end;
  725.   {}
  726.   begin
  727.     TEST;
  728.   end.
  729.  
  730.  A2: Most often you would be compiling a Turbo Pascal program
  731. using the IDE (Integrated Development Environment). If you have
  732. precompiled units you must see to it that you have informed the IDE
  733. of the path to them.
  734.    Press F10 and invoke the "Options" menu (or press alt-O). Select
  735. "Directories...". Press tab two times to get to "Unit directories"
  736. and edit the path accordingly. Here is what I have entered myself
  737.   EXE & TPU directory  r:\
  738.   Include directories  r:\
  739.   Unit directories     f:\progs\turbo70\tpu70
  740.   Object directories   f:\progs\turbo70\tpu70
  741. As you see I keep all my precompiled Turbo Pascal 7.0 units in the
  742. f:\progs\turbo70\tpu70 directory.
  743. --------------------------------------------------------------------
  744.  
  745. From ts@uwasa.fi Mon Jan 1 00:01:14 1996
  746. Subject: Beginners' pointers
  747.  
  748. 74. *****
  749.  Q: What are and how do I use pointers?
  750.  
  751.  A: This is a beginner's simplified introduction. A pointer is a
  752. variable type used to hold the address of another variable, that is
  753. to point to it. Pointers are used to
  754.  1) To refer to and manipulate variables indirectly.
  755.  2) In Turbo Pascal to obtain access to the heap storage area, which
  756.     is not restricted to 64Kbytes.
  757. Consider the following example
  758.   {$M 16384,0,80000}
  759.   var yPtr : ^real;
  760.   begin
  761.     New(yPtr);
  762.     yPtr^ := 3.14159;
  763.     writeln ('2 times pi = ',  2.0 * yPtr^);
  764.     Dispose(yPtr);
  765.     yPtr := nil;
  766.   end.
  767. Before we can discuss pointers we have to consider some rudiments of
  768. what a kind of a memory model a compiled Turbo Pascal program uses.
  769. This is a highly simplified presentation. For a more detailed
  770. presentation of the TP memory model see for example Tischer (1990b).
  771.   +-------------------------+
  772.   | Heap                    |
  773.   |-------------------------|
  774.   | Data Segment            |
  775.   |-------------------------|
  776.   | Code                    |
  777.   |-------------------------|
  778.   | Program Segment Prefix  |
  779.   +-------------------------+
  780. When you write and compile a Turbo Pascal program it usually
  781. consists of (this is a simplification!) of the three lowest parts.
  782. When you define a global variable, it goes to the Data Segment. For
  783. example defining at the beginning of your program
  784.  var x : real;
  785. requires 6 bytes from the data segment. (Local variables are placed
  786. on the stack.)
  787.    Now, the catch is that because of the underlying 16-bit nature of
  788. MS-DOS, the size of the data segment cannot exceed 64Kb. On occasion
  789. the 64Kb is insufficient. However, if you use pointers, the
  790. corresponding variable values are held on the heap instead of the
  791. data segment or the stack. Before you can use the heap, you have to
  792. reserve it for your program. The following compiler directive makes
  793. a heap of 80000 bytes available to your program {$M 16384,0,80000}.
  794. (The syntax is {$M Stack size, Low heap limit, High heap limit}).
  795.    With pointers you do not refer to a variable directly, but you
  796. point to it. For example, define
  797.   var yPtr : ^real;
  798. Before you can use this pointer, you have to create this new dynamic
  799. variable as follows:
  800.   New(yPtr);
  801. The New(yPtr) statement "Creates a new dynamic variable and sets a
  802. pointer variable to point to it." This pointer, yPtr, will point to
  803. the actual value, which the program puts on the heap. In your
  804. program you can write, for example
  805.   yPtr^ := 3.14159;
  806. Think about the difference between yPtr and yPtr^. The former
  807. contains the value of the memory address where you now have put the
  808. value 3.14159. The latter gives that value. Hence yPtr^ can be used
  809. like any ordinary real variable. The difference is that it is on the
  810. heap, not on the data segment (or stack). Thus you can now use this
  811. pointer. For example you n write
  812.  writeln ('2 times pi = ',  2.0 * yPtr^);
  813. When you do not need the pointer any more in your program you can
  814. dispose of it to release the memory allocated for other purposes:
  815.   Dispose(yPtr);
  816.   yPtr := nil;
  817. "After a call to Dispose, the value of yPtr is undefined and it is
  818. an error to reference yPtr. The reserved word nil denotes a pointer
  819. type constant that does not point to anything." Setting yPtr := nil
  820. is just good programming practice, because then you can later easily
  821. test whether the pointer is available or not. Disposing of a pointer
  822. within your program is not necessary unless the amount of memory is
  823. a critical consideration in your program. The heap will be released
  824. when your program terminates.
  825.   To recount. What yPtr actually contains is the memory address of
  826. the value on the heap. When you write yPtr^, the caret indicates
  827. that you do not mean the pointer itself, but the pointed memory
  828. location in the heap. In this example that memory location in the
  829. heap was made to contain 3.14159.
  830.    You can also define the pointer types. Our second example
  831. illustrates. It displays the squares from one to ten.
  832.   {$M 16384,0,80000}
  833.   type arrayType = array [1..10] of real;
  834.   type arrayPtrType = ^arrayType;
  835.   var A : arrayPtrType;
  836.       i : integer;
  837.   begin
  838.     if SizeOf(arrayType) > MaxAvail then begin
  839.       writeln ('Out of memory');
  840.       halt;
  841.     end;
  842.     New(A);
  843.     for i := 1 to 10 do A^[i] := i*i;
  844.     writeln (A^[9]);
  845.   end.
  846. For an actual application using pointers, see the item "How can I
  847. copy a file in a Turbo Pascal program?"
  848. --------------------------------------------------------------------
  849.  
  850. From ts@uwasa.fi Mon Jan 1 00:01:15 1996
  851. Subject: Reading errorlevel
  852.  
  853. 75. *****
  854.  Q: How can I read another program's errorlevel value in TP?
  855.  
  856.  A: This question is best answered by an example. Here is a very
  857. elementary program that returns errorlevel 14 on exiting.
  858.   program faq2;
  859.   begin
  860.     writeln ('Hello world...');
  861.     halt(14);
  862.   end.
  863. Below is the program that calls FAQ2.EXE and detects its errorlevel.
  864.   {$M 2000,0,0}
  865.   uses Dos;
  866.   begin
  867.     SwapVectors;
  868.     Exec ('r:\faq2.exe', '');  (* Execution *)
  869.     SwapVectors;
  870.     WriteLn('...back from Exec');
  871.     if DosError <> 0 then
  872.       WriteLn('Dos error #', DosError)
  873.       else
  874.       WriteLn('Success; child process errorlevel = ', lo(DosExitCode));
  875.   end.
  876. The output should be
  877.   Hello world...
  878.   ...back from Exec
  879.   Success; child process errorlevel = 14
  880. --------------------------------------------------------------------
  881.  
  882. From ts@uwasa.fi Mon Jan 1 00:01:16 1996
  883. Subject: Usenet Pascal newsgroups
  884.  
  885. 76. *****
  886.  Q: What are the current Pascal newsgroups on the Usenet news?
  887.  
  888.  A: The following new Pascal newsgroups were created June 12, 1995
  889. to replace the old comp.lang.pascal. The following new Delphi
  890. newsgroups were created around July 10, 1995.
  891.  
  892. A special note about Delphi postings. Please use the new delphi
  893. newsgroups for the Delphi related postings. In particular, don't let
  894. the names mislead you. The newsgroup comp.lang.pascal.borland does
  895. NOT cover Delphi.
  896.  
  897. A second special note. Please avoid crossposting between the Pascal
  898. newsgroups. In particular do not crosspost between the old
  899. comp.lang.pascal and the new Pascal newsgroups. It is slows the
  900. transition to the new system. (This automatic posting breaches the
  901. non-crossposting tenet only because it is relevant information about
  902. the arrangements of all the Pascal newsgroups.)
  903.  
  904. NEW:
  905.  comp.lang.pascal.ansi-iso Pascal according to ANSI and ISO standards.
  906.  comp.lang.pascal.borland  Borland's Pascal incl. Turbo Pascal (not Delphi!)
  907.  comp.lang.pascal.mac      Macintosh based Pascals.
  908.  comp.lang.pascal.misc     Pascal in general and ungrouped Pascals.
  909.  
  910.  comp.lang.pascal.delphi.databases     Database aspects of Borland Delphi.
  911.  comp.lang.pascal.delphi.components    Writing components in Borland Delphi.
  912.  comp.lang.pascal.delphi.misc          General issues with Borland Delphi.
  913.  
  914. RELATED of potential interest:
  915. comp.os.msdos.programmer.turbovision Borland's text application libraries
  916.  
  917. OLD:
  918.  comp.lang.pascal          Discussion about Pascal. (Please cease using!)
  919.  
  920. For more information about the new Pascal newsgroups please see
  921.  
  922.  52703 Jun 14 21:37 ftp://garbo.uwasa.fi/pc/doc-net/pasgroup.zip
  923.  pasgroup.zip Information about the comp.lang.pascal.* newsgroups
  924.  
  925.  18086 Jul 11 08:18 ftp://garbo.uwasa.fi/pc/doc-net/delphi.zip
  926.  delphi.zip Vote results of the comp.lang.pascal.delphi.* newsgroups
  927.  
  928. If your site is not getting the new Pascal newsgroups, please
  929. contact your own site's newsmaster about the situation.
  930. --------------------------------------------------------------------
  931.  
  932. From ts@uwasa.fi Mon Jan 1 00:01:17 1996
  933. Subject: Capslock status and toggling
  934.  
  935. 77. *****
  936.  Q: How do I detect the CapsLock status, how do I turn it on/off?
  937.  
  938.  A: Here are the relevant Turbo Pascal routines in answer to these
  939. questions.
  940.   {}
  941.   Uses Dos;  { The Dos unit is needed }
  942.   {}
  943.   (* Is CapsLock on *)
  944.   function CAPSONFN : boolean;
  945.   var regs      : registers;
  946.       KeyStatus : byte;
  947.   begin
  948.     FillChar (regs, SizeOf(regs), 0);
  949.     regs.ax := $0200;      { Get shift flags }
  950.     Intr ($16, regs);      { The keyboard interrupt }
  951.     KeyStatus := regs.al;  { AL = shift status bits }
  952.     if (KeyStatus and $40) > 0 then         { bit 6 }
  953.       capsonfn := true
  954.     else
  955.       capsonfn := false;
  956.   end;  (* capsonfn *)
  957.   {}
  958.   (* Set CapsLock. Use true to turn on, false to turn off *)
  959.   procedure CAPS (TurnOn : boolean);
  960.   var keyboardStatus : byte absolute $0040:$0017;
  961.       regs           : registers;
  962.   begin
  963.     if TurnOn then
  964.        keyboardStatus := keyboardStatus or $40
  965.      else
  966.        keyboardStatus := keyboardStatus and $BF;
  967.     { Interrrupt "check for keystroke" to ensure the LED status }
  968.     FillChar (regs, SizeOf(regs), 0);
  969.     regs.ah := $01;
  970.     Intr ($16, regs);
  971.   end;  (* caps *)
  972.   {}
  973. As you see, CapsLock is indicated by bit 6. The other toggles can be
  974. handled in an equivalent way using this information about the memory
  975. location Mem[$0040:$0017]:
  976.   ScrollLock = bit 4      $10  $EF
  977.   NumLock    = bit 5      $20  $DF
  978.   CapsLock   = bit 6      $40  $BF
  979. --------------------------------------------------------------------
  980.  
  981. From ts@uwasa.fi Mon Jan 1 00:01:18 1996
  982. Subject: Detecting F11 and F12
  983.  
  984. 78. *****
  985.  Q: How do I detect if the F11 or F12 key has been pressed?
  986.  
  987.  A: Here is a sample program
  988.   uses Dos;
  989.   (* Enhanced keyboard ReadKey, no Crt unit needed. Detects also F11
  990.      and F12, and distinguishes between the numeric keypad and the
  991.      gray keys. Lower part of the word returns the first scan code,
  992.      the higher part the second *)
  993.   function RDENKEFN : word;
  994.   var regs     : registers;
  995.       keyboard : byte absolute $40:$96;
  996.   begin
  997.     rdenkefn := 0;
  998.     if ((keyboard shr 4) and 1) = 0 then exit;
  999.     FillChar (regs, SizeOf(regs), 0);
  1000.     regs.ah := $10;
  1001.     Intr ($16, regs);
  1002.     rdenkefn := regs.ax;
  1003.   end;  (* rdenkefn *)
  1004.   {}
  1005.   procedure TEST;
  1006.   var key : word;
  1007.   begin
  1008.     while Lo(key) <> 27 do  { esc exits }
  1009.       begin
  1010.         key := RDENKEFN;
  1011.         if (Lo(key) = 0) and (Hi(key) = 133) then
  1012.           writeln ('F11 was pressed');
  1013.         if (Lo(key) = 0) and (Hi(key) = 134) then
  1014.           writeln ('F12 was pressed');
  1015.       end;
  1016.   end;
  1017.   {}
  1018.   begin TEST; end.
  1019. --------------------------------------------------------------------
  1020.  
  1021. From ts@uwasa.fi Mon Jan 1 00:01:19 1996
  1022. Subject: Substrings from a string
  1023.  
  1024. 79. *****
  1025.  Q: How do I extract (parse) substrings from an input string?
  1026.  
  1027.  A: Carefully study these two routines which I have included in
  1028.  19593 Jun 1 12:12 ftp://garbo.uwasa.fi/pc/research/simirr10.zip
  1029.  simirr10.zip Deriving IRR from ARR: A Simulation Testbench, TS+IV
  1030. They use space (and anything in ascii below it) as the separator.
  1031. Change the while tests if you wish to have a different set of
  1032. separators.
  1033.   (* Number of substrings in a string *)
  1034.   function PARSENFN (sj : string) : integer;
  1035.   var i, n, p : integer;
  1036.   begin
  1037.     p := Length(sj);
  1038.     n := 0;
  1039.     i := 1;
  1040.     repeat
  1041.       while (sj[i] <= #32) and (i <= p) do Inc(i);
  1042.       if i > p then begin parsenfn := n; exit; end;
  1043.       while (sj[i] > #32) and (i <= p) do Inc(i);
  1044.       Inc(n);
  1045.       if i > p then begin parsenfn := n; exit; end;
  1046.     until false;
  1047.   end;  (* parsenfn *)
  1048.   {}
  1049.   (* Get substrings from a string *)
  1050.   function PARSERFN (sj : string; PartNumber : integer) : string;
  1051.   var i, j, n, p : integer;
  1052.       stash      : string;
  1053.   begin
  1054.     if (PartNumber < 1) or (PartNumber > PARSENFN(sj)) then
  1055.       begin PARSERFN := ''; exit; end;
  1056.     p := Length(sj);
  1057.     n := 0;
  1058.     i := 1;
  1059.     repeat
  1060.       while (sj[i] <= #32) and (i <= p) do Inc(i);
  1061.       Inc(n);
  1062.       if n = PartNumber then
  1063.         begin
  1064.           j := 0;
  1065.           while (sj[i] > #32) and (i <= p) do
  1066.             begin
  1067.               Inc(j);
  1068.               stash[0] := chr(j);
  1069.               stash[j] := sj[i];
  1070.               Inc(i);
  1071.             end;
  1072.           PARSERFN := stash;
  1073.           exit;
  1074.         end
  1075.        else
  1076.          while (sj[i] > #32) and (i <= p) do Inc(i);
  1077.     until false;
  1078.   end;  (* parserfn *)
  1079.   {}
  1080.   {... A separate, but useful function from the same package ...}
  1081.   (* Delete trailing white spaces etc rubble from a string *)
  1082.   function TRAILFN (sj : string) : string;
  1083.   var i : byte;
  1084.   begin
  1085.     i := Length (sj);
  1086.     while (i > 0) and (sj[i] <= #32) do i := i - 1;
  1087.     sj[0] := chr(i); trailfn := sj;
  1088.   end;  (* trailfn *)
  1089.   {}
  1090.   {... Another separate, but useful function from the same package ...}
  1091.   (* Delete leading white spaces etc subble from a string *)
  1092.   function LEADFN (sj : string) : string;
  1093.   var i, p : byte;
  1094.   begin
  1095.     p := Length (sj); i := 1;
  1096.     while (i <= p) and (sj[i] <= #32) do i := i + 1;
  1097.     leadfn := Copy (sj, i, p-i+1);
  1098.   end;  (* leadfn *)
  1099. --------------------------------------------------------------------
  1100.  
  1101. From ts@uwasa.fi Mon Jan 1 00:01:20 1996
  1102. Subject: Size of a file
  1103.  
  1104. 80. *****
  1105.  Q: How do I find out the size of any kind of a file?
  1106.  
  1107.  A1: Well, to begin with the FileSize keyword and an example code
  1108. are given in the manual (and help function of later TP versions) so
  1109. those, as usual, are the first places to look at. But the example
  1110. solution can be somewhat improved, and there is also an alternative
  1111. solution. The FSIZEFN should never be applied on an open file.
  1112.   function FSIZEFN (filename : string) : longint;
  1113.   var fle    : file of byte;  { declare as a file of byte }
  1114.       fmSave : byte;
  1115.   begin
  1116.     fmSave := FileMode;       { save the current filemode }
  1117.     FileMode := 0;            { to handle also read-only files }
  1118.     assign (fle, filename);
  1119.     {$I-} reset (fle); {$I+}  { to do your own error detection }
  1120.     if IOResult <> 0 then begin
  1121.       fsizefn := -1; FileMode := fmSave; exit;
  1122.     end;
  1123.     fsizefn := FileSize(fle);
  1124.     close (fle);
  1125.     FileMode := fmSave;       { restore the original filemode }
  1126.   end; (* fsizefn *)
  1127.  
  1128.  A2: The second, general alternative is
  1129.   uses Dos;
  1130.   function FSIZE2FN (FileName : string) : longint;
  1131.   var FileInfo : SearchRec;   { SearchRec is declared in the Dos unit }
  1132.   begin
  1133.     fsize2fn := -1;           { return -1 if anything goes wrong }
  1134.     FindFirst (filename, AnyFile, FileInfo);
  1135.     if DosError <> 0 then exit;
  1136.     if (FileInfo.Attr and VolumeId = 0) and
  1137.        (FileInfo.Attr and Directory = 0) then
  1138.          fsize2fn := FileInfo.Size;
  1139.   end;  (* fsize2fn *)
  1140.  
  1141.  A3: The third alternative is due to a Usenet posting by Wayne
  1142. Hoxsie (hoxsiew@crl.com). This alternative is an instructive example
  1143. of using file handles.
  1144.   uses dos;
  1145.   var f : file;
  1146.   {}
  1147.   function filelength (var f : file) : longint;
  1148.   var
  1149.     handle : ^word;
  1150.     regs : registers;
  1151.   begin
  1152.     handle := @f;
  1153.     fillchar (regs, SizeOf(regs), 0);   { just in case }
  1154.     regs.ax := $4202;
  1155.     regs.bx := handle^;
  1156.     regs.cx := 0;
  1157.     regs.dx := 0;
  1158.     msdos(regs);
  1159.     filelength := (longint(regs.dx) SHL 16)+regs.ax;
  1160.   end;
  1161.   {}
  1162.   begin
  1163.     assign(f,paramstr(1));
  1164.     filemode := 0;  { read-only files too }
  1165.     reset(f);
  1166.     writeln(filelength(f));
  1167.     close(f);
  1168.   end.
  1169. --------------------------------------------------------------------
  1170.  
  1171. From ts@uwasa.fi Mon Jan 1 00:01:21 1996
  1172. Subject: Formatting graphics output
  1173.  
  1174. 81. *****
  1175.  Q: How do I format graphics output like in textmode writeln?
  1176.  
  1177.  A: In the graphics mode the positioned text output procedure is
  1178. OutTextXY (X ,Y : integer; TextString : string); It does not have
  1179. the same output formatting capabilities as the write procedure. It
  1180. only accepts the one TextString. Therefore all the output formatting
  1181. must be done previously on the string. The Str procedure has such
  1182. capabilities. The example below gives the rudiments.
  1183.   uses Crt, Graph;
  1184.   var grDriver : integer;
  1185.       grMode   : integer;
  1186.       ErrCode  : integer;
  1187.       s, s1    : string;
  1188.       v1       : real;
  1189.   begin
  1190.     grDriver := Detect;
  1191.     InitGraph (grDriver, grMode, ' ');
  1192.     ErrCode := GraphResult;
  1193.     if ErrCode <> grOk then begin
  1194.       Writeln ('Graphics error:', GraphErrorMsg(ErrCode)); halt; end;
  1195.     ClearDevice;
  1196.     {}
  1197.     { Writing text in the graphics mode }
  1198.     { Set the drawing color }
  1199.     SetColor (Yellow);
  1200.     { Set the current background color }
  1201.     SetBkColor (Black);
  1202.     { Set style for text output in graphics mode }
  1203.     SetTextStyle (DefaultFont, HorizDir, 2);
  1204.     { Preprocess the text }
  1205.     v1 := 2.345;
  1206.     Str (v1 : 10:2, s1);
  1207.     s := 'The first value is' + s1 + '.';
  1208.     { Output the text }
  1209.     OutTextXY (100, 30, s);
  1210.     OutTextXY (100, 50, 'Press any key');
  1211.     {}
  1212.     repeat until KeyPressed;
  1213.     {}
  1214.     RestoreCrtMode;
  1215.     writeln ('That''s all folks');
  1216.     CloseGraph;
  1217.   end.
  1218. Besides not having the same output formatting capabilities OutTextXY
  1219. and OutText procedures do not scroll the screen. If you wish to
  1220. achieve such an effect, you will have to code it yourself step by
  1221. step. You can see the effect in
  1222.  111673 Oct 8 1993 ftp://garbo.uwasa.fi/pc/ts/tsdemo16.zip
  1223.  tsdemo16.zip Assorted graphics demonstrations of functions etc
  1224. Coding the scrolling is a straight-forward but a laborious task.
  1225. Hence it is beyond this FAQ. The outline, however, is that you must
  1226. keep track where on the screen you are. When you come to the bottom
  1227. of your window you have to move the above region upwards before you
  1228. output new text. You can move graphics regions using the ImageSize,
  1229. GetImage and PutImage procedures.
  1230.   As for readln-type input in a graphics mode, that is a complicated
  1231. issue. You will have to build the input routine reading a character
  1232. at a time with ReadKey. The rudiments of using ReadKey are shown in
  1233. the first question of FAQPAS.TXT. The demo, referred to a few lines
  1234. back, will show the effect.
  1235. --------------------------------------------------------------------
  1236.  
  1237. From ts@uwasa.fi Mon Jan 1 00:01:22 1996
  1238. Subject: Reading more than one key
  1239.  
  1240. 82. *****
  1241.  Q: How do I detect if more than one standard key is pressed down?
  1242.  
  1243.  A: The example code below relies very heavily on a Usenet posting
  1244. by Lou Duchez ljduchez@en.com who wishes to acknowledge Bill Seiler
  1245. for the handling of ports. The KeyNrDown and TEST routines are by
  1246. myself. Besides being a demonstration the TEST procedure can be used
  1247. to get the scan codes of the different keys instead of relying on
  1248. external documentation.
  1249.   Uses Dos;
  1250.   {}
  1251.   var keydown: array[0..127] of boolean;   { status array }
  1252.       oldkbdint: procedure;       { points to the "normal" keyboard handler }
  1253.       port60h, port61h: byte;     { used within the interrupt for storage }
  1254.   {}
  1255.   { The replacement keyboard handler }
  1256.   procedure newkbdint; interrupt;
  1257.   begin
  1258.     port60h := port[$60];
  1259.     keydown[port60h and $7f] := (port60h <= $7f);
  1260.     port61h := port[$61];
  1261.     port[$61] := port61h or $80;
  1262.     port[$61] := port61h;
  1263.     port[$20] := $20;
  1264.   end;
  1265.   {}
  1266.   { Get the scancode of the key pressed down, 128 for none }
  1267.   function KeyNrDown : byte;
  1268.   var i : byte;
  1269.   begin
  1270.     KeyNrDown := 128;
  1271.     for i := 0 to 127 do if KeyDown[i] then KeyNrDown := i;
  1272.   end;
  1273.   {}
  1274.   { Test by displaying the scan codes of the keys pressed }
  1275.   procedure TEST;
  1276.   var k, k1 : byte;
  1277.   begin
  1278.     k1 := 128;
  1279.     repeat
  1280.       k := KeyNrDown;
  1281.       if k <> k1 then begin
  1282.         write (k, ' ');
  1283.         if (k1 = 30) and (k = 31) then writeln ('Pressed A and S ');
  1284.         k1 := k;
  1285.       end;
  1286.     until k = $01; {escape}
  1287.   end; {test}
  1288.   {}
  1289.   begin
  1290.     { turn on the replacement keyboard handler }
  1291.     fillchar(keydown, 128, #0);  { sets array to all "false" }
  1292.     getintvec($09, @oldkbdint);  { record location of old keyboard int }
  1293.     setintvec($09, @newkbdint);  { this line installs the new interrupt }
  1294.     {}
  1295.     TEST;
  1296.     {}
  1297.     { turn off the replacement keyboard handler }
  1298.     setintvec($09, @oldkbdint);
  1299.   end.
  1300. --------------------------------------------------------------------
  1301.  
  1302. From ts@uwasa.fi Mon Jan 1 00:01:23 1996
  1303. Subject: Volume Serial Number
  1304.  
  1305. 83. *****
  1306.  Q: How can I read a disk's Volume Serial Number?
  1307.  
  1308.  A: The Volume Serial Number for disks was introduced in MS-DOS
  1309. version 4.0. Here is an example code
  1310.   uses Dos;
  1311.   {}
  1312.   (* Convert a longint to a hexadecimal string *)
  1313.   function LHEXFN (decimal : longint) : string;
  1314.   const hexDigit : array [0..15] of char = '0123456789ABCDEF';
  1315.   var i         : byte;
  1316.       hexString : string;
  1317.   begin
  1318.     FillChar (hexString, SizeOf(hexString), ' ');
  1319.     hexString[0] := chr(8);
  1320.     for i := 0 to 7 do
  1321.       hexString[8-i] := HexDigit[(decimal shr (4*i)) and $0F];
  1322.     lhexfn := hexString;
  1323.   end;  (* lhexfn *)
  1324.   {}
  1325.   (* Get disk serial number. Requires MS-DOS 4.0+.
  1326.      Else, or on an error, returns an empty string.
  1327.      The default drive can be pointed to by using '0' *)
  1328.   function GETSERFN (drive : char) : string;
  1329.   type diskInfoRecordType =
  1330.     record
  1331.       infoLevel      : word;                   { zero }
  1332.       serialNumber   : longint;                { DWORD actually }
  1333.       volumeLabel    : array [1..11] of char;  { NO NAME if none present }
  1334.       filesystemType : array [1..8] of char;   { FAT12 or FAT16 }
  1335.     end;
  1336.   var regs     : registers;
  1337.       diskInfo : diskInfoRecordType;
  1338.       serial   : string;
  1339.   begin
  1340.     getserfn := '';
  1341.     if swap(DosVersion) < $0400 then exit;
  1342.     FillChar (regs, SizeOf(regs), 0);
  1343.     drive := UpCase (drive);
  1344.     if drive <> '0' then if (drive < 'A') or (drive > 'Z') then exit;
  1345.     regs.ah := $69;             { Interrrupt 21 function $69 }
  1346.     regs.al := $00;             { subfunction: get serial number }
  1347.     if drive <> '0' then
  1348.       regs.bl := ord(drive) - ord('A') + 1
  1349.       else regs.bl := 0;
  1350.     regs.ds := Seg(diskInfo);   { the diskInfo address: }
  1351.     regs.dx := Ofs(diskInfo);   { its segment and offset }
  1352.     Intr ($21, regs);
  1353.     if (regs.flags and FCarry) <> 0 then exit;  { CF is set on error }
  1354.     serial := LHEXFN (diskInfo.serialNumber);
  1355.     getserfn := Copy (serial, 1, 4) + '-' + Copy (serial, 5, 4);
  1356.   end;  (* getserfn *)
  1357.   {}
  1358.   begin
  1359.     writeln ('C: ', GETSERFN('C'));
  1360.   end.
  1361.  
  1362.  A2: The second alternative has been modified from a posting by
  1363. Robert B. Clark rclark@su1.in.net. I have also utilized INTERRUP.E
  1364. from Ralf Brown's listing of interrupt calls
  1365.  ftp://garbo.uwasa.fi/pc/programming/inter48b.zip
  1366.   {}
  1367.   uses Dos;
  1368.   function GETSERFN2 (drive : char): longint;
  1369.   var ParBlock : array [0..24] of char;  { IOCTL parameter block Table 0785 }
  1370.       regs     : registers;
  1371.       sernum   : longint;
  1372.   begin
  1373.     FillChar (ParBlock, SizeOf(ParBlock), 0);
  1374.     FillChar (regs, SizeOf(regs), 0);
  1375.     regs.ax := $440D;     { IOCTL - generic block device request }
  1376.     if drive <> '0' then  { '0' points to the default drive }
  1377.       regs.bl := ord(UpCase(drive)) - ord('A') + 1  { drive as byte }
  1378.       else regs.bl := 0;
  1379.     regs.ch := $08;       { block device IOCTL category code: disk drive }
  1380.     regs.cl := $66;       { IOCTL minor code: get volume serial number }
  1381.     regs.ds := Seg(ParBlock);   { Parameter block segment address }
  1382.     regs.dx := Ofs(ParBlock);   { Parameter block offset }
  1383.     MsDos (regs);         { Call interrupt $21 }
  1384.     if regs.Flags and FCarry = 0 then
  1385.       sernum := word(ord(ParBlock[4]) + ord(ParBlock[5]) shl 8) * 65536 +
  1386.                 word (ord(ParBlock[2]) + ord(ParBlock[3]) shl 8)
  1387.     else sernum := 0;
  1388.     getserfn2 := sernum;
  1389.   end;  (* getsetfn2 *)
  1390.   {}
  1391.   begin
  1392.     writeln ('C: ', LHEXFN(GETSERFN2('0')));
  1393.   end.
  1394.  
  1395.  A3: Setting a disk's serial number, instead of just reading it, is
  1396. more complicated and will not be covered here. If you need it, the
  1397. routine without source code is available (for floppies only for
  1398. security reasons) as
  1399.   "SETSER Set floppy's serial number (MsDos 4.0+)"
  1400. in TSUNTK.TPU in ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip
  1401.  
  1402. --------------------------------------------------------------------
  1403.  
  1404. From ts@uwasa.fi Mon Jan 1 00:01:24 1996
  1405. Subject: Disabling the keyboard
  1406.  
  1407. 84. *****
  1408.  Q: How can I disable and then enable the keyboard in my TP program?
  1409.  
  1410.  A: Here is the code. A warning! Don't experiment with ports. You
  1411. can do real harm to your data and your computer if you do not know
  1412. exactly what you are doing.
  1413.   uses Dos, Crt;  { Crt only needed because of 'Delay' in the testing }
  1414.   var i : byte;   { only needed in the testing }
  1415.       NormalKeyboard : procedure;
  1416.   {}
  1417.   procedure DisableKeyboard; interrupt;
  1418.   var port60, port61 : byte;
  1419.   begin
  1420.     port60 := Port[$60];  { KeyBoard controller data output buffer }
  1421.     port61 := Port[$61];  { Keyboard controller port B }
  1422.     Port[$61] := Port61 or $80;  { clear keyboard }
  1423.     Port[$61] := Port61;
  1424.     Port[$20] := $20;     { Programmable Intr. Contr. initialization }
  1425.   end;
  1426.   {}
  1427.   begin
  1428.     writeln ('Testing...');
  1429.     GetIntVec ($09, @NormalKeyboard);
  1430.     SetIntVec ($09, @DisableKeyboard);
  1431.     write ('The keyboard is now disabled..');
  1432.     for i := 1 to 5 do begin
  1433.       Delay (1000);
  1434.       write (i:2);
  1435.     end; {for}
  1436.     writeln;
  1437.     SetIntVec ($09, @NormalKeyboard);
  1438.     write ('The keyboard is now enabled...');
  1439.     for i := 1 to 5 do begin
  1440.       Delay (1000);
  1441.       write (i:2);
  1442.     end; {for}
  1443.   end.
  1444. --------------------------------------------------------------------
  1445.  
  1446. From ts@uwasa.fi Mon Jan 1 00:01:25 1996
  1447. Subject: CD-ROM device name
  1448.  
  1449. 85. *****
  1450.  Q: How do I get the character device name of the (first) CD-ROM?
  1451.  
  1452.  A: First the code for a quick and dirty method to find the
  1453. character device name
  1454.   function MSCDEXFN : string;
  1455.   var s : string;
  1456.       f : text;
  1457.       i : byte;
  1458.       fmSave : byte;
  1459.   begin
  1460.     mscdexfn := '';                  { To indicate not found }
  1461.     fmSave := FileMode;              { Store the original file mode }
  1462.     FileMode := 0;                   { Also if read-only }
  1463.     Assign (f, 'c:\autoexec.bat');   { Browse the AUTOEXEC.BAT }
  1464.     {$I-} Reset (f); {$I+}
  1465.     if IOResult <> 0 then exit;      { AUTOEXEC.BAT not found }
  1466.     while not eof(f) do begin        { Line by line }
  1467.       readln (f, s);
  1468.       for i := 1 to Length(s) do s[i] := Upcase(s[i]);
  1469.       if Pos('MSCDEX', s) > 0 then begin      { Is this the line }
  1470.         if Pos ('REM', s) = 1 then continue;  { Skip rem lines }
  1471.         Close (f);
  1472.         FileMode := fmSave;          { Restore the original mode }
  1473.         i := Pos('/D:', s);          { Look for the switch }
  1474.         if i = 0 then exit;          { Nah! }
  1475.         i := i + 3;                  { Where the name should start }
  1476.         if i > Length(s) then exit;  { Nothing there! }
  1477.         s := Copy (s, i, 255);       { Rest of the line after /D: }
  1478.         mscdexfn := s;
  1479.         i := Pos (' ', s);
  1480.         if i = 0 then exit;
  1481.         mscdexfn := Copy (s, 1, i-1);
  1482.         exit;                        { Don't close twice }
  1483.       end; {if}
  1484.     end; {while}
  1485.     Close (f);
  1486.     FileMode := fmSave;              { Restore the original mode }
  1487.   end; (* mscdexfn *)
  1488.  
  1489.  A2: There is more general and orthodox solution to finding the
  1490. character device name for the (first)m CD-ROM. This was kindly
  1491. provided to me by Chris Rankin (rankin@shfax1.shef.ac.uk).
  1492.   uses Dos;
  1493.   function GetCDROMDevice : string;
  1494.   const driver_name_len = 8;
  1495.   type
  1496.     sig     = array[1..6] of char;
  1497.     siglet  = array[1..4] of char;
  1498.     signum  = array[1..2] of char;
  1499.     drvname = array[1..driver_name_len] of char;
  1500.     driverstr = string[driver_name_len];
  1501.   type
  1502.     PCDROMDriver = ^TCDROMDriver;
  1503.     TCDROMDriver = record
  1504.                      NextDriver:         PCDROMDriver;
  1505.                      DeviceAttr:         word;
  1506.                      StrategyEntryPoint: word;
  1507.                      INTEntryPoint:      word;
  1508.                      DeviceName:         drvname;
  1509.                      Reserved:           word;
  1510.                      DriveLetter:        byte;
  1511.                      Units:              byte;
  1512.                    case byte of
  1513.                      0: (SigLetters:     siglet;
  1514.                          SigNumbers:     signum);
  1515.                      1: (Signature:      sig)
  1516.                    end;
  1517.     TDriveEntry = record
  1518.                     SubUnit: byte;
  1519.                     Driver:  PCDROMDriver
  1520.                   end;
  1521.   var
  1522.     DeviceList: array[1..26] of TDriveEntry;
  1523.     Regs:       registers;
  1524.     Name:       driverstr;
  1525.   begin
  1526.     with Regs do
  1527.       begin
  1528.         ax := $1500;
  1529.         bx := 0;
  1530.         intr($2f,Regs);      (* Ask for number of CD-ROM drives. *)
  1531.         if bx = 0 then       (* If none, then exit.              *)
  1532.           begin
  1533.             Name[0] := #0;
  1534.             GetCDROMDevice := Name;
  1535.             exit
  1536.           end;
  1537.         ax := $1501;           (* Put information about each CD-ROM *)
  1538.         es := seg(DeviceList); (*  into DeviceList[].               *)
  1539.         bx := ofs(DeviceList);
  1540.         intr($2f,Regs)
  1541.       end;  (* Below: Name of first CD-ROM driver *)
  1542.     Name := DeviceList[1].Driver^.DeviceName;
  1543.     while Name[length(Name)] = ' ' do  (* Strip off trailing blanks.. *)
  1544.       dec(Name[0]);
  1545.     GetCDROMDevice := Name
  1546.   end;
  1547. --------------------------------------------------------------------
  1548.  
  1549. From ts@uwasa.fi Mon Jan 1 00:01:26 1996
  1550. Subject: Ejecting CD-ROM
  1551.  
  1552. 86. *****
  1553.  Q: How do I eject a CD-ROM using a Turbo Pascal program?
  1554.  
  1555.  A: The code for the ejection is given below. Note that it needs the
  1556. MSCDEXFN function from the previous FAQ item.
  1557.   uses Dos;
  1558.   {}
  1559.   procedure EJECT (charDev     : string;
  1560.                    var ok      : boolean;
  1561.                    var errCode : word);
  1562.   var regs        : registers;
  1563.       cdrom       : file;
  1564.       cdCtrlBlock : byte;            { CD-ROM Control Block }
  1565.       handle      : ^word;           { Handle referencing CD-ROM driver }
  1566.   begin
  1567.     Assign (cdrom, charDev);         { Character device for CD-ROM driver }
  1568.     {$I-} Reset (cdrom); {$I+}       { Tackle errors yourself }
  1569.     if IOresult <> 0 then begin      { Exit if file not found }
  1570.       ok := false;
  1571.       errCode := $FFFF;              { Your own arbitrary error code }
  1572.       exit;
  1573.     end;
  1574.     FillChar (regs, SizeOf(regs), 0);  { Just to make sure }
  1575.     regs.ax := $4403;                { Function $44, subfunction $03 }
  1576.     handle  := @cdrom;               { Establish the file handle }
  1577.     regs.bx := handle^;
  1578.     FillChar(CdCtrlBlock, SizeOf(CdCtrlBlock), 0);
  1579.     CdCtrlBlock := $00;              { $00 eject disk; $05 close tray }
  1580.     regs.ds := Seg(CdCtrlBlock);     { ds:dx CD-ROM control block }
  1581.     regs.dx := Ofs(CdCtrlBlock);
  1582.     MsDos (regs);                    { Call interrupt $21 }
  1583.     {$I-} Close (cdrom); {$I+}
  1584.     ok := regs.flags and FCarry = 0; { Success or not? }
  1585.     errCode := regs.ax;              { $01 = invalid function }
  1586.   end;                               { $05 = access denied }
  1587.   {}                                 { $06 = invalid handle }
  1588.   procedure TEST;                    { $0D = invalid data }
  1589.   var ok : boolean;
  1590.       code : word;
  1591.   begin
  1592.     EJECT ('K', ok, code);
  1593.     if ok then writeln ('Success') else writeln ('Error ', code);
  1594.   end;
  1595.   {}
  1596.   begin
  1597.     TEST;
  1598.   end.
  1599.  
  1600. My thanks are due to Miro Wikgren (wikgren@cc.helsinki.fi) who
  1601. pointed out that the "handle referencing character device for CD-ROM
  1602. driver" must be the name given when the CD-ROM driver is loaded in
  1603. CONFIG.SYS and AUTOEXEC.BAT. I could not solve this problem without
  1604. that help in comp.lang.pascal.borland. In fact the previous FAQ item
  1605. was tackled only after the current FAQ item had been solved first.
  1606.  
  1607. A slightly different approach to the file handle by Miro
  1608.   var cdrom : text; { CD-ROM is a character device }
  1609.   handle    : word; { Handle: word, not a pointer }
  1610.   :
  1611.   handle  := TextRec(cdrom).handle;  { Use TP help for more on this }
  1612.   regs.bx := handle;
  1613.   :
  1614. --------------------------------------------------------------------
  1615.  
  1616. From ts@uwasa.fi Mon Jan 1 00:01:27 1996
  1617. Subject: Detecting ANSI.SYS
  1618.  
  1619. 87. *****
  1620.  Q: How do I find out if the ANSI.SYS driver has been loaded?
  1621.  
  1622.  A: The source code of the relevant function is given below.
  1623. However, this is not necessarily a good solution. First, it requires
  1624. at least MS-DOS version 4.0. Second, there are other, compatible
  1625. screen drivers like ZANSI.SYS. You probably are more interested if
  1626. such a screen driver has been installed rather than if it is
  1627. ANSI.SYS in particular. To find out if any compatible screen driver
  1628. is operative use ISANSIFN from TSUNTG.TPU from
  1629.  112570 Aug 16 1994 ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip
  1630.  tspa3470.zip Turbo Pascal 7.0 real mode units for (real:-) programmers
  1631.   uses Dos;
  1632.   function ANSIOKFN : boolean;
  1633.   var regs : registers;
  1634.   begin
  1635.     if swap(DosVersion) < $0400 then begin
  1636.       writeln ('Error: MS-DOS 4+ required');
  1637.       ansiokfn := false;
  1638.       halt;
  1639.     end;
  1640.     FillChar (regs, SizeOf(regs), 0);
  1641.     regs.ax := $1A00;
  1642.     Intr ($2F, regs);
  1643.     ansiokfn := regs.al = $FF;
  1644.   end; (* ansiokfn *)
  1645. --------------------------------------------------------------------
  1646.  
  1647. From ts@uwasa.fi Mon Jan 1 00:01:28 1996
  1648. Subject: TP tutorial and books
  1649.  
  1650. 88. *****
  1651.  Q: Where do I find Turbo Pascal tutorials and/or good textbooks?
  1652.  
  1653.  A: I'll list some useful sources. The first one (where also this
  1654. item comes from) among other things contains a slightly outdated
  1655. list of TP textbooks.
  1656.  
  1657.  ftp://garbo.uwasa.fi/pc/ts/tsfaqp29.zip
  1658.  tsfaqp29.zip Common Turbo Pascal Questions and Timo's answers
  1659.  
  1660.  ftp://garbo.uwasa.fi/pc/turbopas/tptutrXX.zip
  1661.  tptutrXX.zip Glenn Grotzinger's ascii-text Turbo Pascal Tutor
  1662.  
  1663.  ftp://garbo.uwasa.fi/pc/turbopas/tpr-book.zip
  1664.  tpr-book.zip Electronic Turbo Pascal Reference freeware book
  1665.  
  1666.  ftp://garbo.uwasa.fi/pc/doc-net/faqclpb.zip
  1667.  faqclpb.zip comp.lang.pascal.borland newsgroup Mini-FAQ
  1668.  
  1669. Furthermore, you should see the fine SWAG (SourceWare Archival
  1670. Group's) collection of TP sources. Available from the /pc/turbopas
  1671. directory at Garbo. For the current references to the SWAG files see
  1672. ftp://garbo.uwasa.fi/pc/INDEX.ZIP.
  1673.    Yet another useful source can be the Turbo Pascal WWW pages. You
  1674. can find some of them by connecting to my WWW home page. Its address
  1675. is http://uwasa.fi/~ts. Select my collection of HTTP links and
  1676. proceed to the programming section on the link list.
  1677. --------------------------------------------------------------------
  1678.  
  1679. From ts@uwasa.fi Mon Jan 1 00:01:29 1996
  1680. Subject: Making an executable
  1681.  
  1682. 89. *****
  1683.  Q: How dow I make an executable of my Turbo Pascal source program?
  1684.  
  1685.  A: This is a typical beginner's frequent question which belies not
  1686. having read the manual carefully. You DO have the manual, right? If
  1687. you are using Turbo Pascal 7.0 this is explained on page 48 of the
  1688. User's Guide in the paragraph "Choosing a destination". Here, in
  1689. brief, is what you should do
  1690.   Press F10 to go to the main menu (or press alt-C)
  1691.   Choose Compile
  1692.   Choose Destination Disk  (toggle with enter)
  1693. To direct where the executable should go
  1694.   Press F10 to go to the main menu (or press alt-O)
  1695.   Choose Options
  1696.   Choose Directories...
  1697.   Edit the item EXE & TPU directory   (the destination directory)
  1698. --------------------------------------------------------------------
  1699.  
  1700. From ts@uwasa.fi Mon Jan 1 00:01:30 1996
  1701. Subject: Last byte of a file
  1702.  
  1703. 90. *****
  1704.  Q: How can I quickly read the last byte of a file?
  1705.  
  1706.  A: Below is the code for a relevant procedure. It has a number of
  1707. instructive details for you to look into. It is easy to expand this
  1708. procedure into showing any byte counted from the end by substituting
  1709. the 1 in Seek (f, fs-1) to the inverted position, and by taking care
  1710. that the position is not outside the file.
  1711.   procedure LASTBYTE (fname  : string; var lb : byte);
  1712.   var f      : file;       { Use an untyped file designation }
  1713.       fmSave : byte;       { To push and pop the FileMode }
  1714.       fs     : longint;    { For file size }
  1715.   begin
  1716.     fmSave := FileMode;    { Push the original FileMode }
  1717.     FileMode := 0;         { To enable reading also read-only files }
  1718.     Assign (f, fname);
  1719.     {$I-} Reset (f, 1); {$I+}     { Open file and set record size to 1 }
  1720.     if IOResult <> 0 then begin
  1721.       writeln ('Error opening file ', fname);
  1722.       halt;
  1723.     end;
  1724.     fs := FileSize(f);     { Get the size of the file }
  1725.     if fs = 0 then begin
  1726.       writeln ('Empty file ', fname);
  1727.       halt;
  1728.     end;
  1729.     Seek (f, fs-1);        { Position to the last byte of the file }
  1730.     BlockRead (f, lb, 1);  { Read the value of the position into lb }
  1731.     Close (f);             { Close the file }
  1732.     FileMode := fmSave;    { Pop the original FileMode }
  1733.   end; (* lastbyte *)
  1734. --------------------------------------------------------------------
  1735.  
  1736.